#ifndef AER_DEVICEBUFFER_HPP_
#define AER_DEVICEBUFFER_HPP_

#include "aerDeviceResource.hpp"


namespace aer {
  
class DeviceBuffer : public DeviceResource
{
  public:
    enum Target
    {
      ARRAY               = GL_ARRAY_BUFFER,
      ELEMENT_ARRAY       = GL_ELEMENT_ARRAY_BUFFER,
      TEXTURE             = GL_TEXTURE_BUFFER,
      TRANSFORM_FEEDBACK  = GL_TRANSFORM_FEEDBACK_BUFFER,
      UNIFORM             = GL_UNIFORM_BUFFER,
      
      COPY_READ           = GL_COPY_READ_BUFFER,
      COPY_WRITE          = GL_COPY_WRITE_BUFFER,
      
      PIXEL_PACK          = GL_PIXEL_PACK_BUFFER,
      PIXEL_UNPACK        = GL_PIXEL_UNPACK_BUFFER
    };
  
    enum Usage
    {
      // modified once and used at most a few times
      STREAM_DRAW = GL_STREAM_DRAW,
      STREAM_READ = GL_STREAM_READ,
      STREAM_COPY = GL_STREAM_COPY,
      
      // modified once and used many times
      STATIC_DRAW = GL_STATIC_DRAW,
      STATIC_READ = GL_STATIC_READ,
      STATIC_COPY = GL_STATIC_COPY,
      
      // modified repeatedly and used many times
      DYNAMIC_DRAW = GL_DYNAMIC_DRAW,
      DYNAMIC_READ = GL_DYNAMIC_READ,
      DYNAMIC_COPY = GL_DYNAMIC_COPY
    };
  
    enum Access
    {
      READ_ONLY  = GL_READ_ONLY,
      WRITE_ONLY = GL_WRITE_ONLY,
      READ_WRITE = GL_READ_WRITE
    };
    
    
  private:
    Target m_target;
    GLuint m_bufferSize;
    
  
  public:
    explicit DeviceBuffer(Target target = ARRAY) // todo: don't put default array !
        : DeviceResource(), 
          m_target(target),
          m_bufferSize(0u)
    {}
    
    
    void generate()
    {
      AER_ASSERT( !isGenerated() );
      glGenBuffers( 1, &m_id);
    }
    
    void release()
    {
      if (isGenerated()) 
      {
        glDeleteBuffers( 1, &m_id);
        m_id = 0u;
      }
    }
        
    void bind() const
    {
      AER_ASSERT( isGenerated() );
      glBindBuffer( m_target, m_id);
    }
    
    void bind(Target target)
    {
      m_target = target;
      bind();
    }
    
    void unbind() const
    {
      glBindBuffer( m_target, 0u);
    }
    
        
    // ===========
    // + Setters +
    // ===========    
    void setTarget(Target target) { m_target = target; }    
        
    void setData(const void *data, const unsigned int bufferSize, const Usage usage=STATIC_DRAW)
    {
      m_bufferSize = bufferSize;
      glBufferData( m_target, bufferSize, data, usage);
    }
    
    void setSubData(const void *data, const unsigned int bufferSize, const unsigned int offset=0u)
    {
      AER_ASSERT( m_bufferSize >= offset + bufferSize );
      glBufferSubData( m_target, offset, bufferSize, data);
    }
    
    void resize(const unsigned int bufferSize, const Usage usage=STATIC_DRAW)
    {
      setData( 0, bufferSize, usage);
    }   
    
    
    // ===========
    // + Getters +
    // ===========    
    unsigned int getSize() const 
    { 
      return m_bufferSize;
    }    
    
    Target getTarget() const 
    { 
      return m_target; 
    }
    
    void getData(void *data, const unsigned int bufferSize, const unsigned int offset)
    {
      AER_ASSERT( m_bufferSize <= bufferSize + offset );
      glGetBufferSubData( m_target, offset, bufferSize, data);
    }    
    
    
    // ===========
    // + Mapping +
    // ===========    
    void map(Access access, void *pDst)
    {
      //AER_ASSERT( pDst != NULLPTR );
      AER_CHECK( "Not tested yet!" && 0 );
      
      pDst = glMapBuffer( m_target, access);
    }
    
    void unmap()
    {
      glUnmapBuffer( m_target );
    }
    
    // ========
    // + Copy +
    // ========
    // TODO
};
  
} // aer

#endif // AER_DEVICEBUFFER_HPP_
